package com.ksana.context.support;

import cn.hutool.core.lang.Assert;
import com.ksana.bean.BeanException;
import com.ksana.bean.factory.config.BeanFactoryPostProcessor;
import com.ksana.bean.factory.config.BeanPostProcessor;
import com.ksana.bean.factory.config.ConfigurableListableBeanFactory;
import com.ksana.bean.factory.support.DefaultListableBeanFactory;
import com.ksana.context.ApplicationEvent;
import com.ksana.context.ApplicationEventPublisher;
import com.ksana.context.ApplicationListener;
import com.ksana.context.ConfigurableApplicationContext;
import com.ksana.context.event.*;
import com.ksana.core.DefaultResourceLoader;

import java.util.*;

public abstract class AbstractApplicationContext extends DefaultResourceLoader implements ConfigurableApplicationContext, ApplicationEventPublisher {
    private Thread shutdownHook;
    private final static String SHUTDOWN_HOOK_THREAD_NAME = "shutdown hook thread name";
    private final Object startupShutdownMonitor = new Object();
    private ApplicationEventMulticaster applicationEventMulticaster;

    @Override
    public void refresh() {
        try {
            ConfigurableListableBeanFactory beanFactory = obtainFreshBeanFactory();
            prepareBeanFactory(beanFactory);
            invokeBeanFactoryPostProcessors(beanFactory);
            registerBeanPostProcessor(beanFactory);
            initApplicationEventMulticaster();
            registerListeners();
            finishRefresh();
        } catch (Exception ex) {
            destroyBeans();
        }
    }

    protected void prepareBeanFactory(ConfigurableListableBeanFactory beanFactory) {
        beanFactory.addBeanPostProcessor(new ApplicationContextAwareProcessor(this));
    }

    protected void invokeBeanFactoryPostProcessors(ConfigurableListableBeanFactory beanFactory) throws BeanException {
        Map<String, BeanFactoryPostProcessor> beanFactoryPostProcessorMap = getBeansOfType(BeanFactoryPostProcessor.class);
        for (String beanName : beanFactoryPostProcessorMap.keySet()) {
            beanFactoryPostProcessorMap.get(beanName).postProcessBeanFactory(beanFactory);
        }

    }

    protected void initApplicationEventMulticaster() {
        this.applicationEventMulticaster = new SimpleApplicationEventMulticaster();
    }

    protected void registerBeanPostProcessor(ConfigurableListableBeanFactory beanFactory) throws BeanException {
        Map<String, BeanPostProcessor> beanPostProcessorMap = getBeansOfType(BeanPostProcessor.class);
        for (String beanName : beanPostProcessorMap.keySet()) {
            beanFactory.addBeanPostProcessor(beanPostProcessorMap.get(beanName));
        }
    }

    protected void registerListeners() throws BeanException {
        Map<String, ApplicationListener> map = getBeansOfType(ApplicationListener.class);
        for (String str : map.keySet()) {
            this.applicationEventMulticaster.addApplicationListener(map.get(str));
        }
    }

    @Override
    public void publishEvent(Object event) {
        if (event instanceof ApplicationEvent) {
            this.applicationEventMulticaster.multicastEvent((ApplicationEvent) event);
        }
    }

    protected void finishRefresh() {
        publishEvent(new ContextRefreshedEvent(this));
    }

    @Override
    public void registerShutdownHook() {
        if (this.shutdownHook == null) {
            this.shutdownHook = new Thread(SHUTDOWN_HOOK_THREAD_NAME) {
                @Override
                public void run() {
                    synchronized (startupShutdownMonitor) {
                        doClose();
                    }
                }
            };
            Runtime.getRuntime().addShutdownHook(this.shutdownHook);
        }
    }

    @Override
    public void close() {
        synchronized (this.startupShutdownMonitor) {
            doClose();
            if (this.shutdownHook != null) {
                try {
                    Runtime.getRuntime().removeShutdownHook(this.shutdownHook);
                }
                catch (IllegalStateException ex) {
                }
            }
        }
    }

    public abstract ConfigurableListableBeanFactory getBeanFactory() throws IllegalStateException;

    protected void destroyBeans() {
        getBeanFactory().destroySingletons();
    }

    protected void doClose() {
        publishEvent(new ContextClosedEvent(this));
        destroyBeans();
        closeBeanFactory();
    }

    protected abstract void closeBeanFactory();

    protected abstract void refreshBeanFactory();

    protected ConfigurableListableBeanFactory obtainFreshBeanFactory() {
        refreshBeanFactory();
        return getBeanFactory();
    }

    @Override
    public Object getBean(String name) throws BeanException {
        return getBeanFactory().getBean(name);
    }

    @Override
    public Object getBean(String name, Object... args) throws BeanException {
        return getBeanFactory().getBean(name, args);
    }

    @Override
    public <T> Map<String, T> getBeansOfType(Class<T> type) throws BeanException {
        return getBeanFactory().getBeansOfType(type);
    }
}
